package main

import (
	"arbitrage/persistence"
	"context"
	"errors"

	//"gitee.com/hacker61/go-binance/v2"
	"net"

	"gitee.com/hacker61/go-binance/v2/common"
	"gitee.com/hacker61/go-binance/v2/futures"

	//"math"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

type SymbolPrices struct {
	Prices        *map[string]float64
	FuturesPrices *map[string]float64
	FundingRates  *map[string]float64
}

type SymbolTicker struct {
	Symbol      string  //币种
	FuturePrice float64 //U本位合约价格
	Price       float64 //当前市价
	FundingRate float64 //当前资金费率
	Premium     float64 //溢价差
}

type SymbolTickerSlice []SymbolTicker

func (st SymbolTickerSlice) Len() int {
	return len(st)
}

func (st SymbolTickerSlice) Less(i, j int) bool {
	return st[i].Premium+st[i].FundingRate < st[j].Premium+st[j].FundingRate
}

func (st SymbolTickerSlice) Swap(i, j int) {
	st[i], st[j] = st[j], st[i]
}

var (
	//client *binance.Client
	//futuresClient *futures.Client
	clientPool        ClientPool
	futuresClientPool FuturesClientPool
	ppersistence      persistence.Persistence
)

var (
	bPosition             bool                          //是否持仓
	symbolPosition        persistence.ArbitragePosition //持仓信息
	bOneCanCreatePosition bool                          //是否有交易对符合开仓条件
	sOneCanCreatePosition string                        //符合开仓条件的交易对
	dPremiumStage         float64                       //当前溢价差达到的阶段
	bOneCanCancelPosition bool                          //持仓交易对是否达到平仓条件
)

/*
func createfuturelimitorder(symbol string, quantity float64, price float64) error {
  Debug.Println(strconv.FormatFloat(price, 'f', -1, 64))
  order, err := futuresClient.NewCreateOrderService().
                              Symbol(symbol).
                              Side(futures.SideTypeBuy).
                              Type(futures.OrderTypeLimit).
                              TimeInForce(futures.TimeInForceTypeGTC).
                              Quantity(strconv.FormatFloat(quantity, 'f', 2, 64)).
                              Price(strconv.FormatFloat(price, 'f', -1, 64)).Do(context.Background())
  if err != nil {
    Error.Println(err)
    return err
  }
  Info.Println(order)
  return nil
}

func createfutureorder(td []TradeData, side string) error {
  var quantity = 0.0
  var symbol string
  var reduceOnly = false
  for _, onetd := range td {
    quantity += onetd.Quantity
    symbol = onetd.Symbol
    reduceOnly = onetd.ReduceOnly
  }
  sidetype := futures.SideTypeBuy
  if side == "BUY" {
    sidetype = futures.SideTypeBuy
  } else if side == "SELL" {
    sidetype = futures.SideTypeSell
  }
  order, err := futuresClient.NewCreateOrderService().
                              Symbol(symbol).
                              Side(sidetype).
                              Type(futures.OrderTypeMarket).
                              ReduceOnly(reduceOnly).
                              //TimeInForce(futures.TimeInForceTypeGTC).
                              Quantity(strconv.FormatFloat(quantity, 'f', 2, 64)).Do(context.Background())
  if err != nil {
    Error.Println(err)
    return err
  }

  Info.Println(order)
  return nil
}

func processWithCurPrice(curP chan float64, symbol string) {
  var side string
  //var lastPrice float64 = 0.0
  //var nextPrice float64 = 0.0
  var extremePrice float64 = 0.0
  currentTrade := make([]TradeData, 0)
  buyTradePrices := SymbolBuyTradePrices[symbol]
  buyTradeDataMap := SymbolBuyTradeDataMap[symbol]
  sellTradePrices := SymbolSellTradePrices[symbol]
  sellTradeDataMap := SymbolSellTradeDataMap[symbol]
  retraceV := retrace[symbol]
  for {
    curPrice := <- curP
    // 判断是否有触发新的交易
    if side == "" || side == "BUY" {
      for {
        if len(buyTradePrices) == 0{
          break
        }
        if buyTradePrices[0] >= curPrice {
          currentTrade = append(currentTrade, buyTradeDataMap[buyTradePrices[0]])
          buyTradePrices = buyTradePrices[1:]
          side = "BUY"
        } else {
          break
        }
      }
    }
    if side == "" || side == "SELL" {
      for {
        if len(sellTradePrices) == 0 {
          break
        }
        if sellTradePrices[0] <= curPrice {
          currentTrade = append(currentTrade, sellTradeDataMap[sellTradePrices[0]])
          sellTradePrices = sellTradePrices[1:]
          side = "SELL"
        } else {
          break
        }
      }
    }
    if side == "" {
      continue
    }
    // 设置交易价格
    if extremePrice >= -0.000001 && extremePrice <= 0.000001 {
      extremePrice = curPrice
    }
    // 判断与旧的网格区间关系
    if side == "BUY" {
      if curPrice < extremePrice {
        extremePrice = curPrice
      } else if curPrice >= extremePrice + retraceV {
        // 买入
        //Todo
        Info.Printf("%v--Buy with price: %v\n", time.Now().Format("2006-01-02 15:04:05"), curPrice)
        createfutureorder(currentTrade, side)
        currentTrade = currentTrade[:0]
        side = ""
        //lastPrice = 0.0
        //nextPrice = 0.0
        extremePrice = 0.0
      }
    } else if side == "SELL" {
      if curPrice > extremePrice {
        extremePrice = curPrice
      } else if curPrice <= extremePrice - retraceV {
        // 卖出
        //Todo
        Info.Printf("%v--Sell with price: %v\n", time.Now().Format("2006-01-02 15:04:05"), curPrice)
        createfutureorder(currentTrade, side)
        currentTrade = currentTrade[:0]
        side = ""
        //lastPrice = 0.0
        //nextPrice = 0.0
        extremePrice = 0.0
      }
    }
  }
}
*/

func getLocalIps() ([]string, error) {
	localIps := make([]string, 0)
	addrs, err := net.InterfaceAddrs()

	if err != nil {
		return nil, err
	}

	for _, address := range addrs {
		// 检查ip地址判断是否回环地址
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				localIps = append(localIps, ipnet.IP.String())
			}
		}
	}

	if len(localIps) == 0 {
		return nil, errors.New("Can not find the client ip address!")
	} else {
		return localIps, nil
	}
}

func main_init() {
	//binance.UseTestnet = true
	//futures.UseTestnet = true
	//client = binance.NewClient(conf.ApiKey, conf.SecretKey)
	//futuresClient = binance.NewFuturesClient(conf.ApiKey, conf.SecretKey)
	localIps, err := getLocalIps()
	if err != nil {
		panic(err)
	}
	Info.Printf("LocalIps: %v\n", localIps)
	clientPool = NewClientPool(conf.ApiKey, conf.SecretKey, localIps...)
	futuresClientPool = NewFuturesClientPool(conf.ApiKey, conf.SecretKey, localIps...)
	ppersistence = persistence.PersistenceMysql{}.Init(conf.MysqlOpt.Host, conf.MysqlOpt.Port, conf.MysqlOpt.User, conf.MysqlOpt.Passwd, conf.MysqlOpt.DBName)
	if stateMachine == nil {
		stateMachine = NewStateMachine()
		symbol, spotCount, futureCount, spotPrice, futurePrice, err := ppersistence.QueryArbitragePosition()
		if err != nil {
			bPosition = false
			err = stateMachine.ChangeState(State_NotPosition)
			if err != nil {
				Error.Println(err)
			}
		} else {
			bPosition = true
			sp := persistence.ArbitragePosition{Symbol: symbol, SpotCount: spotCount, FutureCount: futureCount, SpotPrice: spotPrice, FuturePrice: futurePrice}
			err = stateMachine.ChangeState(State_Position, sp)
			if err != nil {
				Error.Println(err)
			}
		}
		//if err != nil {
		//  bPosition = false
		//} else {
		//  bPosition = true
		//  symbolPosition = persistence.ArbitragePosition{Symbol:symbol, SpotCount:spotCount, FutureCount:futureCount, SpotPrice:spotPrice, FuturePrice:futurePrice}
		//}
	}
	bOneCanCreatePosition = false
	bOneCanCancelPosition = false
	dPremiumStage = 0.0
}

func init() {
	conf_init()
	logger_init()
	main_init()
	main_v2_init()
	order_init()
}

func getCurrentFundingRate(wg sync.WaitGroup, iCh chan int, fpiService *futures.PremiumIndexService, chFundingMap chan map[string]float64) {
	defer wg.Done()
	for {
		<-iCh
		premiums, err := fpiService.Do(context.Background())
		if err != nil {
			iCh <- 1
			continue
		}
		fundingMap := make(map[string]float64)
		for _, premium := range premiums {
			if strings.Contains(premium.Symbol, "USDT") {
				fundingRate, err := strconv.ParseFloat(premium.LastFundingRate, 64)
				if err != nil {
					continue
				}
				fundingMap[premium.Symbol] = fundingRate
			}
		}
		chFundingMap <- fundingMap
	}
}

func processWithCurSymbolPrices(chanSymbolPrices chan SymbolPrices) {
	for {
		symbolPrices := <-chanSymbolPrices
		if !bPosition {
			sts := SymbolTickerSlice{}
			var stCanCreate SymbolTicker
			if symbolPrices.FuturesPrices == nil || symbolPrices.Prices == nil {
				continue
			}
			for symbol, fPrice := range *symbolPrices.FuturesPrices {
				cPrice, ok := (*symbolPrices.Prices)[symbol]
				fundingRate := 0.0
				if symbolPrices.FundingRates != nil {
					fundingRate, _ = (*symbolPrices.FundingRates)[symbol]
				}
				if ok {
					premium := (fPrice - cPrice) / cPrice
					st := SymbolTicker{}
					st.Symbol = symbol
					st.FuturePrice = fPrice
					st.Price = cPrice
					st.Premium = premium
					st.FundingRate = fundingRate
					if bOneCanCreatePosition && sOneCanCreatePosition == symbol {
						stCanCreate = st
					}
					sts = append(sts, st)
				}
			}
			sort.Sort(sort.Reverse(sts))
			if len(sts) <= 5 {
				Debug.Println(sts)
			} else {
				Debug.Println(sts[:5])
			}
			if !bOneCanCreatePosition {
				if sts[0].Premium > conf.CreatePremium {
					// sOneCanCreatePosition = sts[0].Symbol
					// bOneCanCreatePosition = true
					// dPremiumStage = float64(int((sts[0].Premium-conf.CreatePremium)/conf.PremiumStage))*conf.PremiumStage + conf.CreatePremium
					Info.Printf("sts[0] = %v, dPremiumStage = %v\n", sts[0], dPremiumStage)
				}
			} else {
				if stCanCreate.Premium > dPremiumStage {
					dPremiumStage = float64(int((stCanCreate.Premium-conf.CreatePremium)/conf.PremiumStage))*conf.PremiumStage + conf.CreatePremium
					Info.Printf("stCanCreate = %v, dPremiumStage = %v\n", stCanCreate, dPremiumStage)
				} else if stCanCreate.Premium < conf.CreatePremium {
					if sts[0].Premium > conf.CreatePremium {
						sOneCanCreatePosition = sts[0].Symbol
						bOneCanCreatePosition = true
						dPremiumStage = float64(int((sts[0].Premium-conf.CreatePremium)/conf.PremiumStage))*conf.PremiumStage + conf.CreatePremium
						Info.Printf("sts[0] = %v, dPremiumStage = %v\n", sts[0], dPremiumStage)
					} else {
						sOneCanCreatePosition = ""
						bOneCanCreatePosition = false
						dPremiumStage = 0.0
						Info.Printf("bOneCanCreatePosition stop, sts[0] = %v\n", sts[0])
					}
				} else {
					if sts[0].Symbol == sOneCanCreatePosition {
						createArbitrageOrder(conf.QuoteOrderQty, sts[0].FuturePrice, sOneCanCreatePosition)
						symbol, spotCount, futureCount, spotPrice, futurePrice, err := ppersistence.QueryArbitragePosition()
						if err != nil {
							bPosition = false
						} else {
							bPosition = true
							symbolPosition = persistence.ArbitragePosition{Symbol: symbol, SpotCount: spotCount, FutureCount: futureCount, SpotPrice: spotPrice, FuturePrice: futurePrice}
						}
					} else {
						sOneCanCreatePosition = sts[0].Symbol
						bOneCanCreatePosition = true
						dPremiumStage = float64(int((sts[0].Premium-conf.CreatePremium)/conf.PremiumStage))*conf.PremiumStage + conf.CreatePremium
						Info.Printf("sts[0] = %v, dPremiumStage = %v\n", sts[0], dPremiumStage)
					}
				}
			}
		} else {
			if symbolPrices.FuturesPrices == nil || symbolPrices.Prices == nil {
				continue
			}
			cPrice, ok := (*symbolPrices.Prices)[symbolPosition.Symbol]
			cFuturesPrice, ok2 := (*symbolPrices.FuturesPrices)[symbolPosition.Symbol]
			if ok && ok2 {
				premium := (cFuturesPrice - cPrice) / cPrice
				Debug.Println(cPrice, cFuturesPrice, premium)
				if !bOneCanCancelPosition {
					if premium < conf.ClosePremium {
						bOneCanCancelPosition = true
						dPremiumStage = float64(int((premium-conf.ClosePremium)/conf.PremiumStage))*conf.PremiumStage + conf.ClosePremium
						Info.Printf("premium = %v, dPremiumStage = %v\n", premium, dPremiumStage)
					}
				} else {
					if premium < dPremiumStage {
						dPremiumStage = float64(int((premium-conf.ClosePremium)/conf.PremiumStage))*conf.PremiumStage + conf.ClosePremium
					} else if premium > conf.ClosePremium {
						bOneCanCancelPosition = false
					} else {
						Debug.Println("Close Arbitrage Order")
						// 达到平仓条件，平仓
						closeArbitrageOrder(symbolPosition.SpotCount, symbolPosition.FutureCount, symbolPosition.Symbol)
						bPosition = false
					}
				}
			}
		}
	}
}

func createArbitrageOrder(quoteQty, futurePrice float64, symbol string) {
	Info.Println(symbol, quoteQty, futurePrice)
	var bCreateOK, bFuturesCreateOK bool
	var cummulativeQuoteQty, executedQty, executedPrice float64        // 实际交易金额（USDT)，实际交易数量，实际交易均价
	var futuresCummuQuoteQty, futuresExecQty, futuresExecPrice float64 //同上，合约
	wg := sync.WaitGroup{}
	wg.Add(1)
	go func() {
		defer wg.Done()
		var err error
		cummulativeQuoteQty, executedQty, executedPrice, err = createBinanceBuyOrder(symbol, quoteQty)
		if err != nil {
			bCreateOK = false
		} else {
			bCreateOK = true
		}
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		var err error
		futuresCummuQuoteQty, futuresExecQty, futuresExecPrice, err = createFuturesSellOrder(symbol, futurePrice, quoteQty)
		if err != nil {
			bFuturesCreateOK = false
		} else {
			bFuturesCreateOK = true
		}
	}()
	wg.Wait()
	Debug.Println(cummulativeQuoteQty, futuresCummuQuoteQty)
	// 如果一个成功另一个失败，成功的也要平掉
	if !bCreateOK && bFuturesCreateOK {
		err := createFuturesBuyOrder(symbol, futuresExecQty)
		if err != nil {
			panic(err)
		}
	} else if !bFuturesCreateOK && bCreateOK {
		err := createBinanceSellOrder(symbol, executedQty)
		if err != nil {
			panic(err)
		}
	} else if bFuturesCreateOK && bCreateOK {
		ppersistence.InsertArbitragePosition(symbol, executedQty, futuresExecQty, executedPrice, futuresExecPrice)
	}
}

func closeArbitrageOrder(qty, futuresQty float64, symbol string) {
	Info.Println(qty, futuresQty, symbol)
	wg := sync.WaitGroup{}
	wg.Add(1)
	go func() {
		defer wg.Done()
		err := createBinanceSellOrder(symbol, qty)
		if err != nil {
			Error.Println(err)
			panic(err)
		}
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		err := createFuturesBuyOrder(symbol, futuresQty)
		if err != nil {
			Error.Println(err)
			panic(err)
		}
	}()
	wg.Wait()
	// 从数据库中删除记录
	//Todo
	err := ppersistence.DeleteArbitragePosition(symbol)
	if err != nil {
		Error.Println(err)
		panic(err)
	}
}

func main_v1() {
	symbolPrices := make(chan SymbolPrices, 1)
	ticker := time.NewTicker(time.Duration(conf.GetPriceDuration) * time.Millisecond)
	wg := sync.WaitGroup{}
	wg.Add(1)
	go func() {
		defer wg.Done()
		//flpService := futuresClient.NewListPricesService()
		//lpService := client.NewListPricesService()
		_ = clientPool.NewListPricesService()
		_ = futuresClientPool.NewListPricesService()
		fpiService := futuresClientPool.One().NewPremiumIndexService()
		iChGetFundingRate := make(chan int, 0)
		chFundingMap := make(chan map[string]float64, 0)
		wg.Add(1)
		go getCurrentFundingRate(wg, iChGetFundingRate, fpiService, chFundingMap)
		wgInner := sync.WaitGroup{}
		for {
			<-ticker.C
			curSymbolPrices := SymbolPrices{}
			wgInner.Add(1)
			go func() {
				defer wgInner.Done()
				//prices, err := flpService.Do(context.Background())
				prices, err := futuresClientPool.NewListPricesService().Do(context.Background())
				if err != nil {
					Warning.Println("futures list prices failed.", err)
					if common.IsAPIError(err) {
						time.Sleep(time.Second)
					}
					return
				}
				tmpFuturesPrices := make(map[string]float64)
				for _, p := range prices {
					if strings.Contains(p.Symbol, "USDT") {
						curprice, err := strconv.ParseFloat(p.Price, 64)
						tmpFuturesPrices[p.Symbol] = curprice
						if err != nil {
							Warning.Println(err)
							continue
						}
					}
				}
				curSymbolPrices.FuturesPrices = &tmpFuturesPrices
			}()
			wgInner.Add(1)
			go func() {
				defer wgInner.Done()
				//prices, err := lpService.Do(context.Background())
				prices, err := clientPool.NewListPricesService().Do(context.Background())
				if err != nil {
					Warning.Println("list prices failed.", err)
					if common.IsAPIError(err) {
						time.Sleep(time.Second)
					}
					return
				}
				tmpPrices := make(map[string]float64)
				for _, p := range prices {
					if strings.Contains(p.Symbol, "USDT") {
						curprice, err := strconv.ParseFloat(p.Price, 64)
						tmpPrices[p.Symbol] = curprice
						if err != nil {
							Warning.Println(err)
							continue
						}
					}
				}
				curSymbolPrices.Prices = &tmpPrices
			}()
			//iChGetFundingRate <- 1
			//fundingMap := <- chFundingMap
			//curSymbolPrices.FundingRates = &fundingMap
			wgInner.Wait()
			select {
			case <-symbolPrices:
				symbolPrices <- curSymbolPrices
			default:
				symbolPrices <- curSymbolPrices
			}
		}
	}()

	var processPriceFunc = func(chanSymbolPrices chan SymbolPrices) {
		defer wg.Done()
		processWithCurSymbolPrices(chanSymbolPrices)
	}
	wg.Add(1)
	go processPriceFunc(symbolPrices)

	wg.Wait()
}

func main() {
	//main_v1()
	main_v2()
}
